﻿/* 
 * 
 * Copyright 2013 Bendsoft. All rights reserved.
 * http://www.bendsoft.com
 * 
 * This code is distributed under the New BSD License (BSD).
 * 
 * @package     Camelot SharePoint Integration Library
 * @codeplex    http://camelottoolkit.codeplex.com
 * @license     New BSD License (BSD)
 * 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Camelot.SharePointIntegration.IntegrationService
{
    public static class DateMaskScheduler
    {
        public static NextDateResult NextDate(string dateMask, DateTime fromDate)
        {
            bool validformat = System.Text.RegularExpressions.Regex.IsMatch(dateMask, @"^(\d{4}(\+\d{1,2})?|#)-(\d{2}(\+\d{1,2})?|#)-(\d{2}(\+\d{1,2})?|#) (\d{2}(\+\d{1,2})?|#):(\d{2}(\+\d{1,2})?|#)(:(\d{2}(\+\d{1,2})?|#))?$");

            if (!validformat)
                throw new Exception("The date mask is invalid");

            try
            {
                return NextDate(dateMask.Split(new[] { '-', ' ', ':' }), fromDate);
            }
            catch (ArgumentOutOfRangeException ex)
            {
                throw new Exception("The date mask is invalid", ex);
            }
        }

        private static NextDateResult NextDate(string[] dateMask, DateTime fromDate)
        {
            int year = 0;
            int month = 0;
            int day = 0;
            int hour = 0;
            int minute = 0;
            int second = 0;

            for (int i = 0; i < dateMask.Length; i++)
            {
                switch (i)
                {
                    case 0:
                        var noyear = false;

                        if (int.TryParse(dateMask[i], out year) == false)
                        {
                            year = fromDate.Year;

                            var match = System.Text.RegularExpressions.Regex.Match(dateMask[i], @"(\d{4})\+(\d{1,2})");

                            if (match.Success)
                            {
                                int start = int.Parse(match.Groups[1].Value);
                                int step = int.Parse(match.Groups[2].Value);

                                noyear = true;
                                for (int y = start; y < 2050; y = y + step)
                                {
                                    if (y >= year)
                                    {
                                        year = y;
                                        noyear = false;
                                        break;
                                    }
                                }
                            }
                        }

                        if (noyear || year < fromDate.Year)
                            return new NextDateResult { Success = false };

                        break;
                    case 1:
                        var nextyear = false;

                        if (int.TryParse(dateMask[i], out month) == false)
                        {
                            if (year == fromDate.Year)
                                month = fromDate.Month;
                            else
                                month = 1;

                            var match = System.Text.RegularExpressions.Regex.Match(dateMask[i], @"(\d{2})\+(\d{1,2})");

                            if (match.Success)
                            {
                                int start = int.Parse(match.Groups[1].Value);
                                int step = int.Parse(match.Groups[2].Value);

                                nextyear = true;
                                for (int m = start; m < 12; m = m + step)
                                {
                                    if (m >= month)
                                    {
                                        month = m;
                                        nextyear = false;
                                        break;
                                    }
                                }
                            }
                        }
                        if (nextyear || year == fromDate.Year && month < fromDate.Month)
                            return NextDate(dateMask, new DateTime(fromDate.Year, 1, 1, 0, 0, 0).AddYears(1));

                        break;
                    case 2:
                        bool nextmonth = false;

                        if (int.TryParse(dateMask[i], out day) == false)
                        {
                            if (year == fromDate.Year && month == fromDate.Month)
                                day = fromDate.Day;
                            else
                                day = 1;

                            var match = System.Text.RegularExpressions.Regex.Match(dateMask[i], @"(\d{2})\+(\d{1,2})");

                            if (match.Success)
                            {
                                int start = int.Parse(match.Groups[1].Value);
                                int step = int.Parse(match.Groups[2].Value);

                                nextmonth = true;
                                for (int d = start; d < DateTime.DaysInMonth(fromDate.Year, fromDate.Day); d = d + step)
                                {
                                    if (d >= day)
                                    {
                                        day = d;
                                        nextmonth = false;
                                        break;
                                    }
                                }
                            }

                        }

                        if (nextmonth || year == fromDate.Year && month == fromDate.Month && day < fromDate.Day)
                            return NextDate(dateMask, new DateTime(fromDate.Year, fromDate.Month, 1, 0, 0, 0).AddMonths(1));

                        break;
                    case 3:
                        bool nextday = false;

                        if (int.TryParse(dateMask[i], out hour) == false)
                        {
                            if (year == fromDate.Year && month == fromDate.Month && day == fromDate.Day)
                                hour = fromDate.Hour;
                            else
                                hour = 0;

                            var match = System.Text.RegularExpressions.Regex.Match(dateMask[i], @"(\d{2})\+(\d{1,2})");

                            if (match.Success)
                            {
                                int start = int.Parse(match.Groups[1].Value);
                                int step = int.Parse(match.Groups[2].Value);

                                nextday = true;
                                for (int h = start; h < 24; h = h + step)
                                {
                                    if (h >= hour)
                                    {
                                        hour = h;
                                        nextday = false;
                                        break;
                                    }
                                }
                            }
                        }

                        if (nextday || year == fromDate.Year && month == fromDate.Month && day == fromDate.Day && hour < fromDate.Hour)
                            return NextDate(dateMask, new DateTime(fromDate.Year, fromDate.Month, fromDate.Day, 0, 0, 0).AddDays(1));

                        break;
                    case 4:
                        bool nexthour = false;

                        if (int.TryParse(dateMask[i], out minute) == false)
                        {
                            if (year == fromDate.Year && month == fromDate.Month && day == fromDate.Day && hour == fromDate.Hour)
                                minute = fromDate.Minute;
                            else
                                minute = 0;

                            var match = System.Text.RegularExpressions.Regex.Match(dateMask[i], @"(\d{2})\+(\d{1,2})");

                            if (match.Success)
                            {
                                int start = int.Parse(match.Groups[1].Value);
                                int step = int.Parse(match.Groups[2].Value);

                                nexthour = true;
                                for (int m = start; m < 60; m = m + step)
                                {
                                    if (m >= minute)
                                    {
                                        minute = m;
                                        nexthour = false;
                                        break;
                                    }
                                }
                            }
                        }

                        if (nexthour || year == fromDate.Year && month == fromDate.Month && day == fromDate.Day && hour == fromDate.Hour && minute < fromDate.Minute)
                            return NextDate(dateMask, new DateTime(fromDate.Year, fromDate.Month, fromDate.Day, fromDate.Hour, 0, 0).AddHours(1));

                        break;
                    case 5:
                        bool nextminute = false;

                        if (int.TryParse(dateMask[i], out second) == false)
                        {
                            if (year == fromDate.Year && month == fromDate.Month && day == fromDate.Day && hour == fromDate.Hour && minute == fromDate.Minute)
                                second = fromDate.Second;
                            else
                                second = 0;

                            var match = System.Text.RegularExpressions.Regex.Match(dateMask[i], @"(\d{2})\+(\d{1,2})");

                            if (match.Success)
                            {
                                int start = int.Parse(match.Groups[1].Value);
                                int step = int.Parse(match.Groups[2].Value);

                                nextminute = true;
                                for (int s = start; s < 60; s = s + step)
                                {
                                    if (s >= second)
                                    {
                                        second = s;
                                        nextminute = false;
                                        break;
                                    }
                                }
                            }
                        }

                        if (nextminute || year == fromDate.Year && month == fromDate.Month && day == fromDate.Day && hour == fromDate.Hour && minute == fromDate.Minute && second < fromDate.Second)
                            return NextDate(dateMask, new DateTime(fromDate.Year, fromDate.Month, fromDate.Day, fromDate.Hour, fromDate.Minute, 0).AddMinutes(1));

                        break;
                }
            }

            return new NextDateResult { Success = true, Date = new DateTime(year, month, day, hour, minute, second) };
        }

        public class NextDateResult
        {
            public bool Success { get; set; }
            public DateTime Date { get; set; }
        }
    }
}
